/*******************************************************************************
 * Copyright (c) 2010-2011 Robert Fraser                                       *
 *                                                                             *
 * Permission is hereby granted, free of charge, to any person obtaining a     *
 * copy of this software and associated documentation files (the "Software"),  *
 * to deal in the Software without restriction, including without limitation   *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,    *
 * and/or sell copies of the Software, and to permit persons to whom the       *
 * Software is furnished to do so, subject to the following conditions:        *
 *                                                                             *
 *   - The above copyright notice and this permission notice shall be included *
 *     in all copies or substantial portions of the Software.                  *
 *                                                                             *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER      *
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     *
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         *
 * DEALINGS IN THE SOFTWARE.                                                   *
 *******************************************************************************/

using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using zeal.core;
using zeal.ui.util;

namespace zeal.ui.graph
{
    [TemplatePart(Name = "PART_header", Type = typeof(FrameworkElement))]
    internal sealed partial class GraphNode : Control
    {
        private readonly GraphEditor _editor;
        private readonly TextureNode _node;
        private readonly GraphPort[] _inPorts;
        private readonly GraphPort[] _outPorts;
        private readonly GraphPort _singleInput;
        private readonly GraphPort _singleOutput;
        private Point? _dragPos;
        private Point _dragStart;
        private FrameworkElement _header;

        public TextureNode node { get { return _node; } }
        public GraphEditor editor { get { return _editor; } }
        public string name { get { return node.info.name; } }
        public double top { get { return Canvas.GetTop(this); } set { Canvas.SetTop(this, value); } }
        public double left { get { return Canvas.GetLeft(this); } set { Canvas.SetLeft(this, value); } }
        public GraphPort[] inPorts { get { return _inPorts; } }
        public GraphPort[] outPorts { get { return _outPorts; } }
        public GraphPort singleInput { get { return _singleInput; } }
        public GraphPort singleOutput { get { return _singleOutput; } }

        public GraphNode(TextureNode node, GraphEditor editor)
        {
            _node = node;
            _editor = editor;
            DataContext = this;
            Bindings.set(this, Panel.ZIndexProperty.of<int>(), this, IsSelectedProperty,
                s => s ? GraphEditor.Z_INDEX_SELECTED_NODES : GraphEditor.Z_INDEX_NODES);
            Bindings.set(this, Canvas.LeftProperty.of<double>(), Bindings.get<double>(node, "x").with(BindingMode.OneWayToSource));
            Bindings.set(this, Canvas.TopProperty.of<double>(), Bindings.get<double>(node, "y").with(BindingMode.OneWayToSource));
            _inPorts = node.inputPorts.Select(port => new GraphPort(this, port)).ToArray();
            _outPorts = node.outputPorts.Select(port => new GraphPort(this, port)).ToArray();
            if (_inPorts.Length == 1) _singleInput = _inPorts[0];
            if (_outPorts.Length == 1) _singleOutput = _outPorts[0];
        }

        public override void  OnApplyTemplate()
        {
            _header = this.getTemplatePart<FrameworkElement>("PART_header");
            _header.MouseLeftButtonDown += onHeaderClicked;
            _header.MouseLeftButtonUp += onHeaderReleased;
            _header.MouseMove += onHeaderMoved;
            _header.LostMouseCapture += onHeaderLostCapture;
        }

        private void onHeaderClicked(object sender, MouseButtonEventArgs args)
        {
            if (Mouse.Captured != null)
                return;
            if (!IsSelected)
            {
                if (GraphEditor.isSelectionModifierKeyDown)
                    IsSelected = !IsSelected;
                else if (!IsSelected)
                {
                    _editor.deselectAll();
                    IsSelected = true;
                }
            }
            _dragPos = _dragStart = args.GetPosition(this);
            Mouse.Capture(_header);
            _editor.onDragBegin();
            args.Handled = true;
        }

        private void onHeaderMoved(object sender, MouseEventArgs args)
        {
            if (!_dragPos.HasValue)
                return;
            if (Mouse.LeftButton != MouseButtonState.Pressed)
            {
                if (Mouse.Captured == _header)
                    Mouse.Capture(null);
                _dragPos = null;
            }
            else
            {
                Point p = args.GetPosition(this);
                Vector v = p - _dragStart;
                if (v.Length == 0)
                    return;
                _dragPos = p;
                _editor.onDragDelta(v.X, v.Y);
                args.Handled = true;
            }
        }

        private void onHeaderReleased(object sender, MouseButtonEventArgs args)
        {
            if (!_dragPos.HasValue)
                return;
            if (Mouse.Captured != _header)
            {
                _dragPos = null;
                return;
            }
            Mouse.Capture(null);
            _dragPos = null;
            _editor.onDragEnd();
            args.Handled = true;
        }

        private void onHeaderLostCapture(object sender, MouseEventArgs e) { _dragPos = null; }
    }
}